﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SSharp.Core.Evaluator;

namespace SSharp.Core.DataTypes {
	/// <summary>
	/// Scheme lists are built from pairs and the empty list instance.
	/// </summary>
	public static class List {
		private static readonly Symbol dotSymbol = new Symbol(".");

		/// <summary>
		/// Creates a scheme list from an array.
		/// </summary>
		public static object Create(params object[] values) {
			return ParseDots(values);
		}

		/// <summary>
		/// Creates a scheme list from an IList.
		/// </summary>
		public static object Create(IEnumerable<object> values) {
			return ParseDots(values.ToArray());
		}

		/// <summary>
		/// Parses dots in the list, and returns the scheme list.
		/// </summary>
		private static object ParseDots(object[] values) {
			for (int i = 0; i < values.Length; i++) {
				if (dotSymbol.Equals(values[i])) {
					if (i > 0
						&& values.Length == i + 2
						&& !dotSymbol.Equals(values[i + 1])) {
						// if the dot is not the first element
						//		and it is the one-before-last element
						//		and the last element is not a dot
						// then this is the standard dotted syntax - list is something like (a b c d . e)
						//		e is then used instead of the empty-list to end (a b c d)
						return BuildList(values.Take(i), values[i + 1]);
					}

					if (i > 0
						&& values.Length > i + 3
						&& !dotSymbol.Equals(values[i + 1])
						&& dotSymbol.Equals(values[i + 2])
						&& !values.Skip(i + 3).Contains(dotSymbol)) {
						// if the dot is not the first element
						//		and it has at least three elements after it
						//		and the element 2 after it is a dot
						//		and no other element is a dot
						// then this is the infix dotted syntax - list is something like (a b . c . d e)
						//		c is then moved to the front of the list to produce (c a b d e)
							return BuildList(
								new[] { values[i + 1] }
								.Concat(values.Take(i))
								.Concat(values.Skip(i + 3)),
								EmptyList.Instance);
					}

					throw new SyntaxError("List: incorrect use of .");
				}
			}

			return BuildList(values, EmptyList.Instance);
		}

		/// <summary>
		/// Builds a list, with the given last element (normally EmptyList.Instace)
		/// </summary>
		private static object BuildList(IEnumerable<object> values, object lastElement) {
			object result = lastElement;
			// loop backwards to generate a forward-pointing list.
			foreach (object value in values.Reverse()) {
				result = new Cons(value, result);
			}

			return result;
		}

		/// <summary>
		/// Creates an IList from a scheme list.
		/// If the list is not a valid list (e.g. it has a non-list cdr in some location), null is returned.
		/// </summary>
		public static IList<object> Unpack(object list) {
			if (list == null) {
				return null;
			}

			List<object> result = new List<object>();

			// go through all the consecutive car's and add them to the result list - until the cdr is not a cons.
			object cdr = list;
			Cons cons = cdr as Cons;
			while (cons != null) {
				result.Add(cons.car);
				cdr = cons.cdr;
				cons = cdr as Cons;
			}

			if (cdr is EmptyList) {
				// valid list
				return result;
			} else {
				// the last element is not () (and not a Cons) - it's not a valid list.
				return null;
			}
		}

		/// <summary>
		/// A singleton that represents the empty list, ()
		/// </summary>
		public class EmptyList {
			public static readonly EmptyList Instance = new EmptyList();

			private EmptyList() { }

			public override int GetHashCode() {
				return 0;
			}

			public override bool Equals(object obj) {
				return obj is EmptyList;
			}

			public override string ToString() {
				return "()";
			}
		}
	}
}
